查看原文
其他

程序丨教程:制作一个类似《神秘海域》的装备系统(上)

2017-05-04 王成林 Gad-腾讯游戏开发者平台

翻译:王成林(麦克斯韦的麦斯威尔 ) 

审校:黄秀美(厚德载物)


《神秘海域》中的装备系统设计的很好,有人要求我使用第三人称角色制作一篇关于类似《神秘海域》中的装备系统的教程。在这篇教程中你将学到如何:

·  将武器附加给第三人称角色

·  建立简单的物品栏系统管理物品

·  丢弃和装备物品

·  使用UMG和物品栏管理器与物品进行通信


该系统基于事件,因此它的效率很高。

该系统非常简单!

本教程使用了虚幻引擎4.10版本。确保你使用相同版本的引擎。


直播流


你可以观看关于该教程建立的直播流:


https://v.qq.com/txp/iframe/player.html?vid=c0399zzccju&width=500&height=375&auto=0


信息


我使用了军事武器白银包中的武器,对于本教程它不是必须的,但是如果你在开发和军事武器相关的游戏我推荐你使用这个包。

本项目以第三人称模板为基础,你可以在新建项目时找到它。

我在市场中发现了相似的系统,但是我不知道它是否是基于事件的。

 

创建插槽(Socket)


我们首先为角色加入插槽。打开SK_Mannequin然后打开骨骼。添加这些插槽:



Right,left,back和backsecond被用作武器的附加点。其余为武器被装备时的位置。

 

基础类


整个系统将使用两个基础类:

·  BP_BaseWeapon

·  InventoryManager

 

新建一个继承自Actor的蓝图,将其命名为BP_BaseWeapon。然后新建一个继承自Actor组件的蓝图,命名为InventoryManager。

先不管它们,我们添加一些基本数据。


AmmoType枚举


新建一个名为AmmoInfo的枚举(Enum),然后加入以下:

·  Pistol

·  Rifle

·  Launcher

该枚举被用来决定武器正在使用哪一种子弹。

 

AttachPoint枚举


建立另一个名为AttachPoint的枚举并加入以下:

·  Right

·  Left

·  Back

·  BackSecond

该枚举被用来决定武器可以佩戴在身体的哪个部位。

 

AttachInfo结构体


新建一个名为AttachInfo的结构体,它应包含:

·  inUse(bool)

·  AttachPoint(AttachPoint)

·  WeaponRef(BP_BaseWeapon)

 

AmmoInfo结构体


添加另一个名为AmmoInfo的结构体然后加入以下:

·  AmmoType(AmmoType枚举)

·  AmmoCount(int)

该结构体被用于储存我们所拥有的各类型子弹的数量


MyLibrary


新建一个名为MyLibrary的函数库。



所有类都能调用库函数,因此它们超级有用。需要注意一点——它们没有世界语境(World Context),意味着像GetPlayerController,GetGameState,GetPlayerPawn之类的节点不能正常工作因为它们需要世界语境。如果你需要世界语境的话你应该将它们传递给函数。函数库可用于储存你在不同类中使用的数学函数。


在我们的例子中我们将建立一些函数,它们根据AttachPoint枚举返回插槽名称。


打开MyLibrary然后加入两个函数。

GetSocketNameByPoint

·  输入:Type(AttachPoint)

·  输出:SocketName(name)

它应该为纯函数



然后加入GetSocketPointByName:

·  输入:Name(name)

·  输出:Type(AttachPoint)

它也应该是纯函数



Switch中的名称应该和在角色网格物体中加入的插槽名称相同。

 

BP_BaseWeapon


现在我们可以开始建立基础类了。打开BP_BaseWeapon然后加入这些组件:

·  名为Box的BoxCollision(盒状碰撞)。同时加入它的OnBeginOverlap事件(该组件应为根组件)

·  名为SkeletalMesh的Skeletal Mesh(骨骼网格物体)。



现在打开事件图表然后加入这些变量:



在事件图表中新建一个名为AttachWeaponToCharacter的自定义事件:

·  输入:SocketName(Name)



目前我们先不管BP_BaseWeapon——我们后面再返回这里。


InventoryManager


打开InventoryManager然后加入这些变量:



SocketStates:



我们定义了该物品栏默认可以支持的“插槽”。


Ammo:



它存储了各种类型弹药的默认数量。

打开事件图表然后加入一个事件调度器(Event Dispatcher):

·  OnInventoryStateUpdated。每当inventorymanager中发生变化时它都会被调用——这样我们可以和拥有InventoryManager的角色进行通信。

 

函数:


现在我们可以加入一些基本的函数了。新建一个名为FindFreeSocket的函数:

·  输入:For Weapon(BP_BaseWeapon引用)

·  输出:FreePoint(AttachPoint)

·  输出:Found?(bool)

·  名为Local_FoundSocket的本地变量(bool)

·  名为Local_SocketPoint的本地变量(AttachPoint)



它检查了武器插槽是否匹配插槽状态以及插槽状态是否为可用。我们不希望将武器附加在已经有武器附加的插槽上。


建立另一个名为MarkSocketAsInUse的函数:

·  输入:Point(AttachPoint)

·  输入:WeaponRef(BP_BaseWeapon引用)

·  本地变量:Local_Index(int)

·  本地变量:Local_NewAttachInfo(AttachInfo)



该函数寻找SocketStates中相匹配的插槽然后将InUse标记为真。这是在蓝图中使用数组结构体的最佳方法了。储存索引,储存新的结构体信息然后设置数组元素。在独立窗口/移动端和已烘焙的编译模式下都可以正常运行。听上去很复杂但是其实超级简单。


现在建立另一个名为MarkSocketAsAvailable的函数,它的功能相同,只是将InUse标记为否:

·  输入:Point(AttachPoint)

·  本地变量:Local_Index(int)

·  本地变量:Local_NewAttachInfo(AttachInfo)



我在这里没有使用WeaponReference,因为我希望将它从SocketStates中清除出去因为我们要将插槽标记为可用于附加。

建立另一个名为AddAmmo的函数:

·  输入:Type(AmmoType)

·  Ammount(int)



这是关于如何使用结构体数组的最简单的例子了。我们想要添加某类武器的弹药。我在数组中搜索匹配的类型,然后储存索引并储存更新后的子弹信息。然后设置数组元素。


例如我们想要增加5颗手枪子弹。它会在Ammo数组中搜索Pistol类型,然后增加子弹数并设置数组元素。


建立另一个名为GetCurrentAmmo的函数:

·  输入:ForType(AmmoType)

·  输出:Ammo(int)

·  可以为纯函数



新建另一个名为SetEquipedWeapon的函数:

·  输入:Weapon(BP_BaseWeapon引用)



该函数只是设置了EquippedWeapon变量。


帮助性建议:当我需要设置不同类中的变量时,我一定使用函数来修改变量。在已烘焙编译模式中设置不同类中的变量会导致崩溃,因为这会产生循环依赖。调用设置变量的函数是个不错的编程方法,你也应该在蓝图中使用该方法。


新建另一个名为ClearEquipedWeapon的函数:



新建另一个名为GetCurrentEquippedWeapon的函数:

·  输出:Weapon(BP_BaseWeapon 引用)

·  输出:Equipping(bool)

·  函数可以为纯函数



该函数返回当前装备的武器和信息,如果有效的话。(有效意味着当前正在装备着某武器)

现在创建一些最重要的函数。新建一个名为AddToInventory的函数:

·  输入:Weapon(BP_BaseWeapon引用) 



这很简单。它搜索了可以装备武器的插槽,并检查它是否可用。如果是的话,更新该插槽,保存武器的引用并对武器调用Attach。

建立最后一个名为RemoveFromInventory的函数:

·  输入:Weapon(BP_BaseWeapon引用)



同样它非常简单。在Socket States中寻找相配的武器,然后将它清除以便再次使用。


事件


我们已经添加了所有需要的函数,现在我们添加三个事件。


新建一个名为Equip的事件,它含有一个输入,类型为BP_BaseWeapon引用:


以及UnEquip事件:


最后是DropItem,有一个输入,类型为BP_BaseWeapon引用:


这就是InventoryManager中的所有内容了!现在我们需要返回BP_BaseWeapon。


确保你已经在第三人称角色蓝图中添加了InventoryManager组件!

 

返回BP_BaseWeapon


返回BP_BaseWeapon。如果你还没有添加Box的OnComponentBeginOverlap事件现在添加它:


这里我们开始向物品栏添加物品了。

现在添加Equip和UnEquip事件:


这应该无需解释吧。

最后一个事件——Drop, 我们将其作为一个占位符。



这就是BP_BaseWeapon中的所有内容了!

 

创建武器


现在我们创建继承自BP_BaseWeapon的Weapon_Rifle, Weapon_Launcher和Weapon_Pistol蓝图并设置它们的变量。


Weapon_Pistol:



Weapon_Rifle:



Weapon_Launcher:



现在将这些武器放在你的关卡中以便你可以捡起它们!

 

创建Ammo Pickups(可拾取子弹)


新建一个继承自Actor的蓝图,将其命名为BP_AmmoPickup。它应该有两个变量:



组件:

·  名为StaticMesh的静态网格物体组件。

·  名为Box的盒状碰撞。加入它的OnComponentBeginOverlap事件 

我们在构造脚本中根据AmmoType改变可拾取物体的网格物体。



你可以使用不同的网格物体。

BoxOverlap会增加子弹数:



就是这样了。现在你可以拾取子弹了!

 

创建UMG


Widget_ItemActions


新建一个名为Widget_ItemActions的控件(Widget)。你在这里可以找到它的层级:


https://v.qq.com/txp/iframe/player.html?vid=f03993vsbzk&width=500&height=375&auto=0


打开事件图表并添加这些变量:



添加一个名为SetInvManager的函数,它有一个InventoryManager引用类型的输入:



添加另一个名为SetItemRef的函数,它有一个BP_BaseWeapon引用类型的输入:



添加一个新的自定义事件,名为UpdateWidget:



在OnClicked(Button_Equip)中:



这里调用了物品和管理器中的equip和unequip事件。然后在OnClicked(Button_Drop)中对武器和管理器调用Drop:



现在添加Tick函数来决定角色是否点击了控件以外的区域,是的话我们就可以关闭它了:



目前(UE4.10)还不能在控件中获取输入。你需要使用玩家控制器(Player Controller)。

这是该控件的所有内容了!


明天将放出这个教程的下半部分,敬请期待!


今日推荐


干货!深度解析游戏引擎开发中的算法

虚幻引擎4:用C++实现一个近战连招系统



添加小编微信,可享双重福利

1.加入GAD程序猿交流基地

获取行业干货资讯,观看大牛分享直播

2.直接领取60G独家程序资料库,地址在小编朋友圈

包括腾讯内部分享、文章教程、视频教程等全套资料

 

↓长按添加小编GAD苏苏↓


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存